package org.gwt.grin.client.features;

import java.util.Map;
import java.util.Set;

import org.gwt.grin.client.Feature;
import org.gwt.grin.client.util.Graphics;

/**
 * An {@code Assembly} is a feature composed of other features. It's a bit like
 * a switch statement: Only one child of an assembly can be active at a time.
 * This can be used to compose a menu that can be in one of several visual
 * states. Often the parts of an assembly are groups.
 * 
 * @author Bill Foote (http://jovial.com)
 * @author ggeorg
 */
public class Assembly extends Feature {

	protected String[] partNames;
	protected Feature[] parts;
	protected Feature currentPart = null;
	protected boolean activated = false;

	// Number of features checked so far for needsMoreSetup()
	private int numSetupChecked;

	public Assembly(String name) {
		super(name);
	}
	
	public void setup(String[] partNames, Feature[] parts) {
		this.partNames = partNames;
		this.parts = parts;
		currentPart = parts[0];
	}

	@Override
	protected Feature createClone(Map<Feature, Feature> clones) {
		if (!isSetup() || activated) {
			throw new IllegalStateException();
		}
		Assembly result = new Assembly(null);
		result.show = show; // FIXME
		int found = -1;
		result.partNames = partNames;
		result.parts = new Feature[parts.length];
		for (int i = 0; i < parts.length; i++) {
			if (currentPart == parts[i]) {
				found = i;
			}
			result.parts[i] = parts[i].makeNewClone(clones);
		}
		if (found != -1) {
			result.currentPart = result.parts[found];
		}
		result.numSetupChecked = numSetupChecked;
		// result.activated remains false
		return result;
		// No initializeClone() of this feature is needed.
	}

	@Override
	public void addSubgraph(Set<Feature> set) {
		if (set.contains(this)) {
			return; // Avoid O(n^2) with assemblies
		}
		super.addSubgraph(set);
		for (int i = 0; i < parts.length; i++) {
			parts[i].addSubgraph(set);
		}
	}

	@Override
	public int getX() {
		return currentPart.getX();
	}

	@Override
	public int getY() {
		return currentPart.getY();
	}

	/**
	 * Get the names of our parts.
	 */
	public String[] getPartNames() {
		return partNames;
	}

	/**
	 * Get our parts, that is, our child features.
	 */
	public Feature[] getParts() {
		return parts;
	}

	@Override
	protected void initialize() {
		// The show will initialize our sub-features, so we don't need to do
		// anything here.
	}

	@Override
	protected void destroy() {
		// The show will destroy our sub-features, so we don't need to do
		// anything here.
	}

	@Override
	protected void setActivateMode(boolean mode) {
		activated = mode;
		if (mode) {
			currentPart.activate();
		} else {
			currentPart.deactivate();
		}
	}

	/**
	 * This should not be directly called by clients of the GRIN framework,
	 * unless TODO...
	 * 
	 * @param feature
	 */
	public void setCurrentPart(Feature feature) {
		if (currentPart == feature) {
			return;
		}
		if (activated) {
			feature.activate();
			currentPart.deactivate();
			show.getDirector().notifyAssemblyPartSelected(this, feature,
					currentPart, activated);
		}
		currentPart = feature;
	}

	@Override
	protected int setSetupMode(boolean mode) {
		if (mode) {
			numSetupChecked = 0;
			int num = 0;
			for (int i = 0; i < parts.length; i++) {
				num += parts[i].setup();
			}
			return num;
		} else {
			for (int i = 0; i < parts.length; i++) {
				parts[i].unsetup();
			}
			return 0;
		}
	}

	@Override
	public boolean needsMoreSetup() {
		// see note about cloned features in setSetupMode()
		while (numSetupChecked < parts.length) {
			if (parts[numSetupChecked].needsMoreSetup()) {
				return true;
			}
			numSetupChecked++;
			// Once a part doesn't need more setup, it will never go
			// back to needing setup until we call unsetup() then
			// setup(). numSetupChecked is re-set to 0 just before
			// callin setup() on our part, so this is safe. Note
			// that the contract of Feature requires that setup()
			// be called before needsMoreSetup() is consulted.
			//
			// This optimization helps speed the calculation of
			// needsMoreSetup() in the case where a group or an
			// assembly is the child of multiple parts of an assembly.
			// With this optimization, a potential O(n^2) is turned
			// into O(n) (albeit typically with a small n).
		}
		return false;
	}

	/**
	 * Find the part of the given name in this assembly, or {@code null} if it
	 * can't be found.
	 * 
	 * @param name
	 *            the part name to search for
	 * @return the part of the given name
	 */
	public Feature findPart(String name) {
		for (int i = 0; i < parts.length; i++) {
			if (partNames[i].equals(name)) {
				return parts[i];
			}
		}
		return null;
	}

	/**
	 * Get the currently active part within this assembly.
	 * 
	 * @see #setCurrentPart(org.gwt.grin.client.hdcookbook.grin.Feature)
	 **/
	public Feature getCurrentPart() {
		return currentPart;
	}

	@Override
	public void markAsChanged() {
		currentPart.markAsChanged();
		// At this point, we're not sure if currentPart refers to the previous
		// frame or the next frame, but either way will generate correct
		// results, because a feature could only fail to be marked as modified
		// if it was active in both.
	}

	@Override
	public void paintFrame(Graphics gc) {
		currentPart.paintFrame(gc);
	}

	@Override
	public void paintDone() {
		currentPart.paintDone();
	}

	@Override
	public void nextFrame() {
		currentPart.nextFrame();
	}
}
